#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <unordered_map>
#include <string>

#define COMMAD_SIZE 1024
#define FORMAT "[%s@%s %s]# "

// 下面是shell定义的全局数据

// 1.命令行参数
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;

// 2.环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;

// 3.别名映射表
std::unordered_map<std::string, std::string> alias_list; // la对于ls -a

// for test
char cwd[1024];
char cwdenv[1024];
char oldpwd[1024];
// last exit code
int lastcode = 0; // 最新子进程退出码

const char *GetUserName()
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName() // 变化不大，可以不用系统调用。其实最好也用系统调用
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

// 切换路径时，环境变量表也要切换。因此我们要更新环境变量。
// 注意我们自定义shell的环境变量表继承自shell
// 路径先变，环境变量才变，调用系统调用
const char *GetPwd()
{
    const char *pwd = getcwd(cwd, sizeof(cwd));
    if (pwd)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetOldPwd()
{
    return getenv("OLDPWD");
}
const char *GetHome()
{
    return getenv("HOME");
}

void InitEnv()
{
    extern char **environ; // 头文件#include <unistd.h>定义了这个，这里是声明
    memset(g_env, 0, sizeof(g_env));
    // 本来从父进程继承，现在直接从配置文件（操作系统）来
    // 1.获取环境变量
    for (int i = 0; environ[i]; i++)
    {
        // 申请空间
        // 别用sizeof，sizeof得到的是指针大小
        g_env[i] = (char *)malloc(strlen(environ[i]) + 1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    g_env[g_envs] = NULL;

    // 2.导入环境变量
    // 增量修改
    for (int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    // environ=g_env;完全重置
}

// command 内建命令
bool Cd()
{
    memset(oldpwd, 0, sizeof(oldpwd));
    snprintf(oldpwd, sizeof(oldpwd), "OLDPWD=%s", GetPwd());
    putenv(oldpwd);
    if (g_argc == 1)
    {
        std::string home = GetHome();
        if (home.empty())
            return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];
        if (where == "-")
            chdir(GetOldPwd());
        else if (where == "~")
            chdir(GetHome());
        else
            chdir(where.c_str());
    }
    return true;
    // cd argc = 1
    if (g_argc == 1)
    {
        std::string home = GetHome();
        if (home.empty())
            return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];
        // cd - / cd ~
        if (where == "-")
        {
            // Todu
        }
        else if (where == "~")
        {
            // Todu
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

void Echo()
{
    if (g_argc == 2)
    {
        std::string opt = g_argv[1];
        if (opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0; // echo执行完，退出码应该是0
        }
        else if (opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if (env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    }
}
// 防止自定义bash路径名太长
/*std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if (dir == SLASH)
        return SLASH; // 只有根目录直接返回
    auto pos = dir.rfind(SLASH);
    return dir.substr(pos + 1);
}*/
// 命令行提示符
void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}
// 打印命令行提示符
void PrintCommandPrompt()
{
    char prompt[COMMAD_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    std::cout << prompt;
    fflush(stdout);
}
bool GetCommandLine(char *out, int size)
{
    char *c = fgets(out, size, stdin);
    if (c == NULL)
        return false;
    out[strlen(out) - 1] = 0; // 清理\n
    if (strlen(out) == 0)
        return false; // 只输入了/n
    return true;
}
// 3.命令行分析 "ls -a -l" 解析为 "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, SEP);
    while(g_argv[g_argc++] = strtok(nullptr, SEP));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for (int i = 0; g_argv[i]; i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc: %d\n", g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if (cmd == "cd")
    {
        Cd();
        return true;
    }
    else if (cmd == "echo")
    {
        Echo();
        return true;
    }
    else if (cmd == "export")
        return true;
    else if (cmd == "alias")
        return true;
    return false;
}

int Execute()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        execvp(g_argv[0], g_argv);
        exit(1); // 一旦替换exit不会执行
    }
    // father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)                        // 等待成功
        lastcode = WEXITSTATUS(status); // 获取子进程退出码
    return 0;
}

int main()
{
    InitEnv();

    while (true) // 死循环
    {
        // 1.输出命令行提示符
        PrintCommandPrompt();

        // 2.获取用户输入的命令
        char commandline[COMMAD_SIZE];
        if (!GetCommandLine(commandline, COMMAD_SIZE))
            continue;

        // 3.命令行分析,并将分析后的命令行导入全面变量表中
        if (!CommandParse(commandline))
            continue;

        // 检测是否是内建命令，若是直接调用然后continue，若不是则执行下面的
        if (CheckAndExecBuiltin())
            continue;
        ;

        Execute();
    }
    return 0;
}
